package com.spzx.common.redis.util;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.List;
import java.util.concurrent.*;

//redis分布式锁     获取锁和释放锁  工具类
public class DistributedLockUtil {
    private static DistributedLockUtil distributedLockUtil;
    private static StringRedisTemplate stringRedisTemplate;

    private DistributedLockUtil() {
    }

    {
        distributedLockUtil = new DistributedLockUtil();
    }

    public static DistributedLockUtil builder(StringRedisTemplate stringRedisTemplate) {
        DistributedLockUtil.stringRedisTemplate = stringRedisTemplate;
        return distributedLockUtil;
    }

    //线程池
    Executor executor = new ThreadPoolExecutor(100, 300, 60,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), Executors.defaultThreadFactory(),
            new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    try {
                        TimeUnit.MILLISECONDS.sleep(200);
                        executor.execute(r);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });


    //看门狗线程:自动续期
    private void watchDog(String lockPrefix, String token, int lockTimeout) {
        executor.execute(() -> {
            try {
                Thread.sleep(2000L * lockTimeout / 3);
                String redisToken = stringRedisTemplate.opsForValue().get(lockPrefix);
                while (token.equals(redisToken)) {
                    stringRedisTemplate.expire(lockPrefix, lockTimeout, TimeUnit.SECONDS);
                    Thread.sleep(2000L * lockTimeout / 3);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    //获取锁
    public void getLock(String lockKey, String token, int lockTimeout) {
        String getLockLuaScript = """ 
                if redis.call('exists', KEYS[1]) == 0
                then redis.call('hset', KEYS[1], ARGV[1], 1)
                redis.call('expire', KEYS[1], ARGV[2]) return 1
                elseif redis.call('hexists', KEYS[1], ARGV[1]) == 1
                then redis.call('hincrby', KEYS[1], ARGV[1],1) return 1
                else return 0 end
                """;
        if (Boolean.TRUE.equals(stringRedisTemplate.execute(new DefaultRedisScript<>(getLockLuaScript, Boolean.class),
                List.of(lockKey), token, lockTimeout))) {
            //获取锁成功 开启看门狗线程
            watchDog(lockKey, token, lockTimeout);
        } else {
            try {
                TimeUnit.SECONDS.sleep(50);
                getLock(lockKey, token,lockTimeout); //递归调用
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //释放锁
    public void unLock(String lockKey, String token) {
        String unLockLuaScript = """
                if redis.call('get', KEYS[1]) == ARGV[1]
                then return redis.call('del', KEYS[1])
                else return 0 end
                """;
        Boolean execute = stringRedisTemplate.execute(new DefaultRedisScript<>(unLockLuaScript, Boolean.class), List.of(lockKey), token);
        System.out.println(Boolean.TRUE.equals(execute) ? "释放锁成功" : "释放锁失败");
    }
}
